package com.brainydroid.datasend;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Enumeration;

import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.util.encoders.Base64;
import org.spongycastle.util.encoders.Hex;


public class Crypto {

    private static final String PROVIDER = "SC";
    private static final String KEYGEN_ALG = "ECDSA";
    private static final String SIGN_ALG = "SHA512withECDSA";
    
    private static final String BEGIN_KEY_BLOCK = "-----BEGIN PUBLIC KEY-----";
    private static final String END_KEY_BLOCK = "-----END PUBLIC KEY-----";
    private static final int LINEWIDTH = 64;

    private static Crypto instance;

    static {
        Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
    }

    private KeyFactory kf;
    private KeyPairGenerator kpg;
    private Signature sg;

    static synchronized Crypto getInstance() {
        if (instance == null) {
            instance = new Crypto();
        }

        return instance;
    }

    private Crypto() {
        try {
            kf = KeyFactory.getInstance(KEYGEN_ALG, PROVIDER);
            kpg = KeyPairGenerator.getInstance(KEYGEN_ALG, PROVIDER);
            sg = Signature.getInstance(SIGN_ALG, PROVIDER);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (NoSuchProviderException e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
	synchronized Enumeration<String> getAvailableCurveNames() {
    	return ECNamedCurveTable.getNames();
    }
    
    synchronized KeyPair generateKeyPairNamedCurve(String curveName) {
        try {
        	ECGenParameterSpec ecParamSpec = new ECGenParameterSpec(curveName);
			kpg.initialize(ecParamSpec);
		} catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
		}

        return kpg.generateKeyPair();
    }

    static String base64Encode(byte[] b) {
        try {
            return new String(Base64.encode(b), "ASCII");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    static String hex(byte[] bytes) {
        try {
            return new String(Hex.encode(bytes), "ASCII");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    static byte[] base64Decode(String str) {
        return Base64.decode(str);
    }

    synchronized PublicKey readPublicKey(String keyStr) throws InvalidKeySpecException {
        X509EncodedKeySpec x509ks = new X509EncodedKeySpec(
                Base64.decode(keyStr));
		return kf.generatePublic(x509ks);
    }
    
    synchronized PublicKey readPublicKey(byte[] key) throws InvalidKeySpecException {
        X509EncodedKeySpec x509ks = new X509EncodedKeySpec(key);
		return kf.generatePublic(x509ks);
    }

    synchronized PrivateKey readPrivateKey(String keyStr) throws InvalidKeySpecException {
        PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec(
                Base64.decode(keyStr));
		return kf.generatePrivate(p8ks);
    }
    
    synchronized PrivateKey readPrivateKey(byte[] key) throws InvalidKeySpecException {
        PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec(key);
		return kf.generatePrivate(p8ks);
    }

    synchronized KeyPair readKeyPair(String pubKeyStr, String privKeyStr) throws InvalidKeySpecException {
        return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr));
    }
    
    synchronized KeyPair readKeyPair(byte[] pubKey, byte[] privKey) throws InvalidKeySpecException {
        return new KeyPair(readPublicKey(pubKey), readPrivateKey(privKey));
    }
    
    static String wrapString(String str, int lineWidth) {
    	if (str.length() <= lineWidth) {
    		return str;
    	} else {
    		return str.substring(0, lineWidth) + "\n" + wrapString(str.substring(lineWidth), lineWidth);
    	}
    }
    
    static String formatKeyString(String keyString) {
    	return BEGIN_KEY_BLOCK + "\n" + wrapString(keyString, LINEWIDTH) + "\n" + END_KEY_BLOCK + "\n";
    }
    
    static String armorPublicKey(PublicKey publicKey) {
    	return formatKeyString(base64Encode(publicKey.getEncoded()));
    }
    
    synchronized byte[] sign(PrivateKey privateKey, byte[] data) throws InvalidKeyException {
    	try {
    		sg.initSign(privateKey);
    		sg.update(data);
    		return sg.sign();
    	} catch (SignatureException e) {
    		throw new RuntimeException(e);
    	}
    }
}
